package RushHour;

import java.util.HashSet;
import java.util.Set;

/**
 * This is a template for the class corresponding to the blocking heuristic.
 * This heuristic returns zero for goal states, and otherwise returns one plus
 * the number of cars blocking the path of the goal car to the exit. This class
 * is an implementation of the <tt>Heuristic</tt> interface, and must be
 * implemented by filling in the constructor and the <tt>getValue</tt> method.
 */
public class BlockingHeuristic implements Heuristic {

	boolean useSimpleHeurstic;

	/**
	 * This is the required constructor, which must be of the given form.
	 */
	public BlockingHeuristic(Puzzle puzzle, boolean useSimpleHeurstic) {
		this.useSimpleHeurstic = useSimpleHeurstic;
	}

	/**
	 * This method returns the value of the heuristic function at the given
	 * state.
	 */
	public int getValue(State state, boolean bWeigtedVersion) {

		// get board
		int[][] Grid = state.getGrid();
		int h = 0;
		int iRedCarEndPos = state.getVariablePosition(0)
				+ state.getPuzzle().getCarSize(0);

		// calculate number of cars blocking the exit of the red car. for each
		// blocking car,
		// calculate the number of cars blocking it. give weighted cost to
		// blocking cars:
		// blocking cars of order 1 cost 1, blocking cars of order 2 cost 2.

		for (int i = iRedCarEndPos; i < state.getPuzzle().getGridSize(); i++) {
			int iFirstOrderBlockingCar = Grid[state.getPuzzle()
					.getFixedPosition(0)][i];

			if (iFirstOrderBlockingCar == -1) // no blocking car on this
												// coordinate
			{
				continue;
			}
			if (!useSimpleHeurstic) {
				// calculate 2nd order blocking cars in North direction.
				for (int j = state.getVariablePosition(iFirstOrderBlockingCar) + 1; j < 0; j--) {
					int iSecondOrderBlockingCar = Grid[j][i];

					if (iSecondOrderBlockingCar == -1) // no blocking car on
														// this coordinate
					{
						continue;
					}

					if (!(bWeigtedVersion && (state.getPuzzle().getCarSize(
							iFirstOrderBlockingCar) == 3)))
						// charging 2nd order blocking cars with factor of 2
						h += 2;
				}

				// calculate 2nd order blocking cars in South direction.
				for (int j = state.getVariablePosition(iFirstOrderBlockingCar)
						+ state.getPuzzle().getCarSize(iFirstOrderBlockingCar); j < state
						.getPuzzle().getGridSize(); j++) {
					int iSecondOrderBlockingCar = Grid[j][i];

					if (iSecondOrderBlockingCar == -1) // no blocking car on
														// this coordinate
					{
						continue;
					}

					// charging 2nd order blocking cars with factor of 2
					h += 2; // charge 3 on weighted version?
				}
			}

			h++; // charge 1 for first order blocking car.
		}

		return h;
	}

	public int getValue(State state) {

		// get board
		int[][] Grid = state.getGrid();
		int h = 0;
		int iRedCarEndPos = state.getVariablePosition(0)
				+ state.getPuzzle().getCarSize(0);
		Set<Integer> foundCars = new HashSet<Integer>();

		// calculate number of cars blocking the exit of the red car. for each
		// blocking car,
		// calculate the number of cars blocking it. give weighted cost to
		// blocking cars:
		// blocking cars of order 1 cost 1, blocking cars of order 2 cost 2.

		for (int i = iRedCarEndPos; i < state.getPuzzle().getGridSize(); i++) {
			int iFirstOrderBlockingCar = Grid[state.getPuzzle()
					.getFixedPosition(0)][i];

			if (iFirstOrderBlockingCar == -1) // no blocking car on this
												// coordinate
			{
				continue;
			}

			h++; // charge 1 for first order blocking car.

			if (useSimpleHeurstic)
				return h;
			
			// if car is a Truck, charge 1 for each car blocking it only in
			// south direction:
			if (state.getPuzzle().getCarSize(iFirstOrderBlockingCar) == 3) {
				// calculate 2nd order blocking cars in South direction.
				for (int j = state.getVariablePosition(iFirstOrderBlockingCar)
						+ state.getPuzzle().getCarSize(iFirstOrderBlockingCar); j < state
						.getPuzzle().getGridSize(); j++) {
					int iSecondOrderBlockingCar = Grid[j][i];

					// no blocking car or blocking car already counted
					if ((iSecondOrderBlockingCar == -1)
							|| (foundCars.contains(iSecondOrderBlockingCar))) {
						continue;
					}

					// charging 2nd order blocking car
					h += 1;
					// add car to found cars
					foundCars.add(iSecondOrderBlockingCar);
				}
			} else {
				int iBottomBlockingCar = state
						.getVariablePosition(iFirstOrderBlockingCar)
						+ state.getPuzzle().getCarSize(iFirstOrderBlockingCar);
				int iTopBlockingCar = state
						.getVariablePosition(iFirstOrderBlockingCar) - 1;

				if ((iTopBlockingCar >= 0)
						&& (Grid[iTopBlockingCar][i] != -1)
						&& ((iBottomBlockingCar < state.getPuzzle()
								.getGridSize()) && Grid[iBottomBlockingCar][i] != -1)) {
					if (foundCars.contains(Grid[iTopBlockingCar][i])
							|| (foundCars.contains(Grid[iBottomBlockingCar][i])))
						continue;

					h += 1;
					foundCars.add(Grid[iTopBlockingCar][i]);
					foundCars.add(Grid[iBottomBlockingCar][i]);

				} else if ((iTopBlockingCar == 0)
						&& ((iBottomBlockingCar < state.getPuzzle()
								.getGridSize()) && (Grid[iBottomBlockingCar][i] != -1))) {
					if (foundCars.contains(Grid[iBottomBlockingCar][i]))
						continue;

					h += 1;
					foundCars.add(Grid[iBottomBlockingCar][i]);
				} else if ((iBottomBlockingCar >= state.getPuzzle()
						.getGridSize())
						&& (iTopBlockingCar >= 0)
						&& (Grid[iTopBlockingCar][i] != -1)) {
					if (foundCars.contains(Grid[iTopBlockingCar][i]))
						continue;

					h += 1;
					foundCars.add(Grid[iTopBlockingCar][i]);
				}
			}

		}

		return h;
	}

}
